/*
 *  $Id: Font.cs 1210 2010-11-23 17:02:00Z thenn.erannor $
 *  This file is a part of Ragima CCG.
 *  (C) 2008-2010 Ragima development group.
 *
 *  Licensed under the terms of the GNU GPL License version 2.
 *
 */

using System;
using System.Collections.Generic;

using AgateLib.BitmapFont;
using AgateLib.DisplayLib;
using AgateLib.Geometry;
using AgateLib.Resources;

namespace Ragima.UI {
    /// <summary>
    /// Шрифт для печати текста. Загружается всегда для текущего языка.
    /// </summary>
    public class Font {
        private FontSurface _font;
        private FontMetrics _metrics;

        public Font(string id) {
            _font = LoadFont(Languages.Language.CurrentLanguage, id);
            _metrics = _font.Impl is BitmapFontImpl ? ((BitmapFontImpl) _font.Impl).FontMetrics : null;
        }

        /// <summary>
        /// Загружает изображение и данные шрифта из ресурсов указанного языка.
        /// </summary>
        private static FontSurface LoadFont(Languages.Language lang, string id) {
            return new FontSurface(lang.GetResources(), lang.GetFontId(id));
        }

        /// <summary>
        /// Оригинальный объект шрифта от AgateLib.
        /// </summary>
        public FontSurface FontSurface {
            get { return _font; }
        }

        /// <summary>
        /// Альфа (прозрачность) шрифта.
        /// </summary>
        public double Alpha {
            get { return FontSurface.Alpha; }
            set { FontSurface.Alpha = value; }
        }
        /// <summary>
        /// Цвет шрифта.
        /// </summary>
        public Color Color {
            get { return FontSurface.Color; }
            set { FontSurface.Color = value; }
        }

        /// <summary>
        /// Проверяет символ, является ли он одним из символов конца строки.
        /// </summary>
        private static bool IsEOL(char ch) {
            return ch == '\n' || ch == '\r';
        }

        /// <summary>
        /// Получает ширину отдельного символа. Если символ отсутствует в шрифте, то возвращает 0.
        /// </summary>
        public int GetWidth(char ch) {
            if (_metrics == null)
                return FontSurface.StringDisplayWidth(ch.ToString());
            GlyphMetrics glyph;
            if (_metrics.TryGetValue(ch, out glyph))
                return glyph.Width;
            else
                return 0;
        }

        /// <summary>
        /// Получает ширину указанного текста.
        /// </summary>
        public int GetWidth(string text) {
            return GetWidth(text, 0, text.Length);
        }

        /// <summary>
        /// Получает ширину указанного диапазона в тексте.
        /// </summary>
        public int GetWidth(string text, int start, int end) {
            if (_metrics == null)
                return FontSurface.StringDisplayWidth(text);
            if (string.IsNullOrEmpty(text))
                return 0;
            int maxWidth = 0, width = 0, last = end - 1;
            for (int i = start; i <= last; i++) {
                char ch = text[i];
                if (IsEOL(ch)) {
                    if (width > maxWidth)
                        maxWidth = width;
                    width = 0;
                    continue;
                }
                GlyphMetrics glyph;
                if (_metrics.TryGetValue(text[i], out glyph)) {
                    if (i != 0)
                        width -= glyph.LeftOverhang;
                    width += glyph.Width;
                    if (i != last)
                        width -= glyph.RightOverhang;
                }
            }
            if (width > maxWidth)
                maxWidth = width;
            return (int) Math.Ceiling(maxWidth * FontSurface.ScaleWidth);
        }

        /// <summary>
        /// Получает позицию в тексте, до которой он влазит в указанную ширину.
        /// </summary>
        public int GetPositionByWidth(string text, int start, int maxWidth) {
            int pos = start, len = text.Length;
            while (pos < len && (GetWidth(text, start, pos + 1) <= maxWidth))
                pos++;
            return pos;
        }
        /// <summary>
        /// Получает часть текста, которая влазит в указанную ширину, без учета слов.
        /// </summary>
        public string GetTextByWidth(string text, int start, int maxWidth) {
            int end = GetPositionByWidth(text, start, maxWidth);
            return text.Substring(start, end - start);
        }

        /// <summary>
        /// Получает обратную позицию (с конца) в тексте, до которой он влазит в указанную ширину.
        /// </summary>
        public int GetEndPositionByWidth(string text, int end, int maxWidth) {
            int pos = end;
            while (pos > 0 && (GetWidth(text, pos - 1, end) <= maxWidth))
                pos--;
            return pos;
        }
        /// <summary>
        /// Получает обратную часть текста (с конца), которая влазит в указанную ширину, без учета слов.
        /// </summary>
        public string GetEndTextByWidth(string text, int end, int maxWidth) {
            int start = GetEndPositionByWidth(text, end, maxWidth);
            return text.Substring(start, end - start);
        }

        /// <summary>
        /// Разделяет текст на несколько строк по ширине с учетом переноса по словам.
        /// Если слово не влазит в допустимую ширину, но влазит в максимальную, то выдает пустой строки с той целью, чтобы слово влезло на следующей строке.
        /// </summary>
        /// <param name="text">Исходный текст</param>
        /// <param name="width">Допустимая ширина строки</param>
        /// <param name="maxWidth">Максимальная ширина строки</param>
        /// <param name="position">Текущая позиция в исходном тексте</param>
        /// <returns>Выделенная строка из текста</returns>
        public string Wrap(string text, int width, int maxWidth, ref int position) {
            int len = text.Length;

            int start = position;
            while (position < len) {
                // проверка на перенос строки
                if (IsEOL(text[position])) {
                    position++;
                    return text.Substring(start, position - 1 - start);
                }

                // проверка на пробел
                if (char.IsWhiteSpace(text, position)) {
                    // если строка с найденным пробелом не влазит, то выдать без него
                    if (GetWidth(text, start, position + 1) > width)
                        break;
                    position++;
                    continue;
                }

                // определение слова
                int wordStart = position;
                while (position < len && !char.IsWhiteSpace(text, position))
                    position++;

                // проверка длины строки со словом: не влпзит по ширине
                if (GetWidth(text, start, position) > width) {
                    // проверка, что слово от начала строки
                    if (start == wordStart) {
                        // проверка на максимальную ширину: если влазит, то оставляем слово на следующую строку
                        if (maxWidth > width && GetWidth(text, start, position) <= maxWidth) {
                            position = start;
                            return string.Empty;
                        }

                        // обратный посимвольный отсчет до максимально подходящей части слова
                        do {
                            position--;
                        } while (GetWidth(text, start, position) > width);
                        break;
                    }

                    // выдача строки без последнего слова
                    position = wordStart;
                    break;
                }
            }

            // получение текущей выделенной строки
            var wrapLine = position != start ? text.Substring(start, position - start) : string.Empty;

            // пропуск пробельных символов в конце строки
            while (position < len && text[position] == ' ')
                position++;

            return wrapLine;
        }

        /// <summary>
        /// Разделяет текст на несколько строк по ширине с учетом переноса по словам.
        /// </summary>
        public IEnumerable<string> Wrap(string text, int maxWidth) {
            int start = 0;
            while (start < text.Length)
                yield return Wrap(text, maxWidth, maxWidth, ref start);
        }

        /// <summary>
        /// Печатает текст в указанном месте.
        /// </summary>
        public void Draw(int x, int y, string text) {
            FontSurface.DrawText(x, y, text);
        }

        /// <summary>
        /// Печатает текст в указанном месте и с указанным выравниванием.
        /// </summary>
        public void Draw(int x, int y, string text, TextAlignment alignment, int width) {
            switch (alignment) {
                case TextAlignment.Center:
                    x += (width - GetWidth(text)) / 2;
                    break;
                case TextAlignment.Right:
                    x += width - GetWidth(text);
                    break;
            }
            FontSurface.DrawText(x, y, text);
        }

        /// <summary>
        /// Печатает текст в указанной области и с указанным выравниванием.
        /// </summary>
        public void Draw(Rectangle rect, string text, TextAlignment alignment) {
            Draw(rect.X, rect.Y, text, alignment, rect.Width);
        }

        /// <summary>
        /// Печатает текст в указанном месте с выравниванием по центру.
        /// </summary>
        public void DrawCenter(int x, int y, string text) {
            Draw(x, y, text, TextAlignment.Center, 0);
        }

        /// <summary>
        /// Печатает текст в указанном месте с выравниванием по правому краю.
        /// </summary>
        public void DrawRight(int x, int y, string text) {
            Draw(x, y, text, TextAlignment.Right, 0);
        }

        /// <summary>
        /// Загружает шрифт из указанного ресурса.
        /// </summary>
        public static object LoadData(string resourceId) {
            var font = new Font(resourceId);
            font.FontSurface.Color = Color.White;
            return font;
        }
    }
}
